/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file level.hpp
 * @author TooOld2Rock'nRoll
 * @date 2023/jan/25
 * @brief Implements the framework for levels management for the ArcadeFighter library.
 *
 * @todo - To handle system events like play/pause, create user events and listen for!
 */
#ifndef _LEVEL_HPP_
#define _LEVEL_HPP_

/*---- Includes ----*/
#include <memory>
#include <stdexcept>
#include <vector>
#include <thread>

#include "config.h"

#include "resource_manager.hpp"
#include "audio/audio.hpp"
#include "camera2D.hpp"
#include "interfaces/game_object.hpp"
#include "character.hpp"


/*---- Enumerations ----*/
extern "C" {
/**
 * @brief These are the minimum expected states for a level.
 *
 * We made it a C style enumeration (no strong type), so you can extend it freely or overwrite it completely and
 *    still be able to override this class virtual methods. Remember, enums in C are just constant integers!<br>
 * You may do so by something like the following example and when you override setState() you just pass original or
 *    extended values interchangeably:
 * @code
 *    typedef enum _extended_level_state_
 *    {
 *        MY_FIRST_STATE = level_state_t::_state_max, //<-- your enumeration will start where level_state ended
 *        MY_SECOND_STATE,
 *        ...
 *        _extend_state_max
 *    } extended_level_state_t;
 * @endcode
 */
typedef enum _level_state_
{
    LEVEL_LOADING = 0, ///<-- for loading resources not suited for the constructor
    EXIT_THREAD, ///<-- if loading resources using a thread, this is a necessary step.
    STARTING, ///<-- maybe add a nice animation and a countdown!
    PAUSE, ///<-- level is paused, stop everything.
    PLAYING, ///<-- level is running normally.
    ENDING, ///<-- maybe add a nice animation before leaving!
    OVER, ///<-- Over as in "Game Over" screen.
    //Exit the Game Loop!!!
    FINISHED, ///<-- game loop will exit
    _level_state_max ///< keeps track of enum size and adds a irregular state
} level_state_e;
} //extern "C"


/**
 * @brief Level class for the ArcadeFighter library.
 *
 * This class offers a basic implementation of a simple level manager.<br>
 * You are encouraged to extend this class with specif behavior for each of your levels.<br>
 * It basically tries to encapsulate the game play experience for a single level,
 *   managing a series of GameObjects and its events in the correct expected order.<br>
 * A good Level instance should be as self contained as possible, it only manages its objects, but has no say on their
 *   behavior.<br>
 * Each level is encouraged to use its own resource domain, so its resources may be allocated and freed just when they
 *   are needed and doesn't clutter the global domain.
 *
 * @remark GameObjects will be printed in the order they were added!
 */
class Level
{
    public:
        /** @brief Bitmask to align a sprite to the level space. */
        enum alignment
        {
            TOP     = 0x000001b,///< Top/Middle
            BOTTOM  = 0x000010b,///< Bottom/Middle
            LEFT    = 0x000100b,///< Left/Middle
            RIGHT   = 0x001000b,///< Right/Middle
            CENTER  = 0x010000b ///< Center
        };//end alignment


    private:


    protected:
        volatile level_state_e _current_state = level_state_e::_level_state_max;///< In which state the level currently is.

        //World information
        unsigned ui_lvl_width = 0, ///< world width in pixels.
                 ui_lvl_height = 0; ///< world height in pixels.

        std::thread *p_loading_thread = nullptr; ///<A thread to load the level resources without blocking the UI.
        ResourceManager *p_res_mgr_instance = nullptr; ///<Local reference to the ResourceManager singleton.
        DomainID_t _res_domain_id = 0; ///<This level unique resource domain id to access the ResourceManager methods.

        EventManager *p_ev_mgr_instance = nullptr; ///<Local reference to the EventManager singleton.
        listenerID_t id_normal_ev_listener = 0; ///<This class ID to access the EventManager services (normal priority).
        /** @brief This class ID to access the EventManager services (high priority).
         *  @remarks It will be inactive unless the child class register it with a callback! */
        listenerID_t id_high_ev_listener = 0;

        Audio *p_audio_instance = nullptr; ///<Local reference to the Audio singleton.

        //Rendering
        Shader *p_common_shader = nullptr; ///<The shader used to load the level resources.
        Camera2D *p_view_window = nullptr; ///<The visible world area.

        std::vector<GameObject *> v_objects_list; ///<The list of objects that are part of this level.

        Character *ap_characters[PLAYERS_COUNT]; ///<The list of playable characters in the level.
        const glm::vec3 *v3pa_character_position[PLAYERS_COUNT]; ///<Keeps track of the characters position in real time

    public:
        Level (unsigned lvl_width, unsigned lvl_height);
        virtual ~Level ();

        /** @brief Sets the level current state.
         * @remark This is a pure virtual method since we understand enforcing states change logic is considered
         *     behavior and, therefore, the user's responsibility to implement.
         * @param new_state - the state to change to.
         * @return <b>true</b> if state change was successful, <b>false</b> if <i>not</i> possible.
         */
        virtual bool setState (level_state_e new_state) = 0;
        /** @brief Gets the level current state.  */
        level_state_e getState () const { return this->_current_state; }

        void addPlayableCharacter (unsigned player_index, Character *pl_char);

        /** @brief Method to load all the necessary resources for the level.
         * Every GameObject part of the level should be loaded here, with exception of playable characters.<br>
         * Remember to change the level state to <b>LOADING</b> at the beginning of the process and change again at
         *   the end to <b>STARTING</b> (or <b>EXIT_THREAD</b>, if loading resources in a thread) if the loading process
         *   succeeded or <b>FINISHED</b> if failed!<br>
         * If using a loading animation, start a thread and let the game loop begging, handle the animation sequence at
         *   the update() method.
         * @remark Be careful calling this method twice since the class resources have already been allocated
         *    and may not behave nicely when reusing the Level at the game loop.
         */
        virtual void load () = 0;

        /** @brief Callback to Handle Events for the Level.
         * @param delta_t - time elapsed since last game loop iteration.
         */
        virtual void processEvents (double delta_t) = 0;
        /** @brief Callback to update the level logic state and registered GameObjects in its different states.
         * @param delta_t - time elapsed since last game loop iteration.
         */
        virtual void update (double delta_t) = 0;
        /** @brief Callback to draw the level scene and registered GameObjects in its different states.
         * @param delta_t - time elapsed since last game loop iteration.
         */
        virtual void draw (double delta_t) = 0;


        /*** helper methods ***/
        void alignSprite2World (Sprite2D &s, Level::alignment al);
        void alignSprite2Camera (Sprite2D &s, Level::alignment al);
};//END Level

#endif //_LEVEL_HPP_

